<?php

if (!defined('BASEPATH'))
    exit('No direct script access allowed');

class Home extends CI_Controller {

    private $runMode = '';

    public function __construct() {
        parent::__construct();
        $this->load->library('wechat');

        /*
         * 运行模式
         * 可选: [test][valid][...]
         * test: 测试模式,将测试用的XML数据引入程序
         * valid: 验证模式,当在微信公众平台开启开发模式时需验证服务器是否配置正确使用
         * ...: 除以上状态外的任意设置都是正常运行模式,将正常接收及响应用户的消息 
         */
        $this->runMode = '';
    }

    public function index() {
        switch ($this->runMode) {
            case 'test':
                //测试模式
                $platform_opn_id = 'platform_opn_id';
                $user_opn_id = 'user_opn_id';

                $content = 'testfun#123123';
//                $content = 'cd#5';
//                $content = 'dc#150';
//                $content = 'gs#3';
//                $content = 'dh#';
//                $content = 'dh#3555555';
//                $content = 'dz#';
//                $content = 'dz#安新南区356栋3楼';
//                $content = 'qr#1';
                $xml = '<xml>
                    <ToUserName><![CDATA[' . $platform_opn_id . ']]></ToUserName>
                    <FromUserName><![CDATA[' . $user_opn_id . ']]></FromUserName>
                    <CreateTime>' . time() . '</CreateTime>
                    <MsgType><![CDATA[text]]></MsgType>
                    <Content><![CDATA[' . $content . ']]></Content>
                    <MsgId>5859477638296371514</MsgId>
                    </xml>';
//                $xml = '<xml>
//                    <ToUserName><![CDATA[' . $platform_opn_id . ']]></ToUserName>
//                    <FromUserName><![CDATA[' . $user_opn_id . ']]></FromUserName>
//                    <CreateTime>' . time() . '</CreateTime>
//                    <MsgType><![CDATA[event]]></MsgType>
//                    <Event><![CDATA[subscribe]]></Event>
//                    <EventKey><![CDATA[]]></EventKey>
//                    </xml>';
                $msg = $this->wechat->resloveMsg($xml);

                if (!is_object($msg)) {
                    exit($msg);
                }

                $this->_MsgByType($msg->msgArr['msgtype'], $msg);
                break;
            case 'valid':
                //验证模式
                $this->wechat->valid($_GET);
                break;
            default :
                //普通运行模式
                $postStr = @$GLOBALS['HTTP_RAW_POST_DATA'];
                if (!$postStr) {
                    echo 'err_no_data_gained';
                    exit;
                }

                $msg = $this->wechat->resloveMsg($postStr);

                if (!is_object($msg)) {
                    exit($msg);
                }

                $this->_MsgByType($msg->msgArr['msgtype'], $msg);
                break;
        }
    }

    /*
     * 黑名单验证
     */

    private function _isBanned($user_opn_id) {
        $this->load->model('blacklists_model', 'bl');

        if (FALSE !== $this->bl->get($user_opn_id)) {
            return TRUE;
        }
        return FALSE;
    }

    /*
     * 按照不同的消息类型处理
     * 目前仅处理文本与事件消息
     */

    private function _MsgByType($msgType, $msg) {
        //黑名单验证
        if ($this->_isBanned($msg->msgArr['user_opn_id'])) {
            /*
             * 用户已被ban,退出
             * 这里可以输出一些信息给用户,我就不做了,有兴趣的话可以实现
             */
            exit('err_user_has_been_banned');
        }

        switch ($msgType) {
            case 'text'://文本
                $this->_MsgText($msg);
                break;
//            case 'image'://图像
//                $this->_MsgImage();
//                break;
//            case 'location'://坐标
//                $this->_MsgLocation();
//                break;
//            case 'link'://超链接
//                $this->_MsgLink();
//                break;
            case 'event'://事件
                $this->_MsgEvent($msg);
                break;
//            case 'voice'://语音
//                $this->_MsgVoice();
//                break;
            default://未知类型
                $this->_sendHelpMsg($msg, 'unknown_msg_type');
                break;
        }
    }

    /*
     * 帮助信息
     * 这里用作没有匹配到消息类型或者没有合适关键字的时候均调用本过程
     * 通常输出一些帮助信息文本
     */

    private function _sendHelpMsg($msg, $debug_msg = '') {
        /*
         * 仅在测试模式下,会将调用本函数时携带的调试信息随着text输出
         * 
         */
        //获取帮助信息
        $k = $this->_getKeyword(0, $this->wechat->help_keyword);
        $r = $this->_getReply($k->id);

        if ('test' == $this->runMode) {
            $r->content .= "\n\n debug_msg:" . $debug_msg;
        }

        $this->_sendMsg($msg, $r);
    }

    /*
     * 获取关键字
     */

    private function _getKeyword($msgtype_id, $keyword) {
        $this->load->model('keywords_model', 'keywords');

        /*
         * 此处getKeyword默认为全匹配模式,同样支持模糊匹配模式
         * 具体请查看keywords_model
         */
        $keywords = $this->keywords->getKeyword($keyword, 0, $msgtype_id, 0);
        if (FALSE !== $keywords) {
            return $keywords[array_rand($keywords)];
        }
        //未查询到合适的关键字
        return FALSE;
    }

    /*
     * 判断用户信息是否含有指定的命令标识
     */

    private function _isTextCmd($msg) {
        //命令标识
        $cmd_sign = $this->wechat->cmd_sign;
        $content = json_decode($msg->msgArr['content'])->Content;
        //检查消息内是否有命令标识
        if (FALSE === strpos($content, $cmd_sign)) {
            return FALSE;
        }
        return TRUE;
    }

    /*
     * 判断命令是否超时
     */

    private function _isExpire($user_datas) {
        $cmd_expire_time = $this->wechat->cmd_expire_time;
        //当前时间 - 用户上次命令时间 > 超时设置 => 已超时
        if (time() - $user_datas->last_time > $cmd_expire_time) {
            return TRUE;
        }
        return FALSE;
    }

    /*
     * 获取命令设置
     */

    private function _getCmd($cmd) {
        $this->load->model('commands_model', 'cmds');

        return $this->cmds->get($cmd, 0);
    }

    /*
     * 获取用户数据
     */

    private function _getDatas($user_opn_id) {
        $this->load->model('datas_model', 'datas');

        $d = $this->datas->get($user_opn_id);

        //查询的用户数据不存在,则新增一条数据
        if (FALSE === $d) {
            if ($this->datas->add($user_opn_id) > 0) {
                return $this->datas->get($user_opn_id);
            }
        }
        return $d;
    }

    /*
     * 处理文本命令信息
     */

    private function _MsgCmd($msg) {
        $matches = NULL;

        //命令标识
        $cmd_sign = $this->wechat->cmd_sign;
        //分解消息
        $msg_content = explode($cmd_sign, json_decode($msg->msgArr['content'])->Content);
        //命令体
        $cmd = $this->_getCmd(strtolower($msg_content[0]));

//        dump($cmd);exit;
        //用户输入的命令不存在
        if (FALSE === $cmd) {
            $this->_sendMsg($msg, $this->_getCmd('err_invlid_command'));
            log_message('error', 'err_invlid_command');
            exit;
        }

        //命令
        $command = $cmd->command;

        //获取用户数据
        $user_datas = $this->_getDatas($msg->msgArr['user_opn_id']);
        //将用户已经保存的数据转化成对象
        $data = json_decode(isset($user_datas->data) ? $user_datas->data : '');
        if (FALSE == $data) {
            //若用户未有保存过的数据则新建一个对象
            $data = new stdClass();
        }

        //检查当前命令是否有超时设置,并检查是否超时
        if (1 === intval($cmd->is_expire)) {
            if ($this->_isExpire($user_datas)) {
                $this->_sendMsg($msg, $this->_getCmd('err_command_expired'));
                log_message('error', 'err_command_expired');
                exit;
            }
        }

        //检查当前输入命令的父命令
        if ($cmd->p_cmd_id > 0) {
            //对比当前命令的父命令是否与用户数据中保存的命令id一致
            if (intval($user_datas->last_cmd_id) !== intval($cmd->p_cmd_id)) {
                $this->_sendMsg($msg, $this->_getCmd('err_incorrect_parent_command'));
                log_message('error', 'err_incorrect_parent_command');
                exit;
            }
        }

        //检查命令参数是否符合规则
        if ('' !== $cmd->data_regex) {
            //检查用户是否输入了参数
            if (empty($msg_content[1])) {
                //检查用户数据中是否保存有对应命令的数据
                if (!isset($data->$command)) {
                    //未保存数据且未输入参数
                    $this->_sendMsg($msg, $this->_getCmd('err_incorrect_command_data'));
                    log_message('error', 'err_incorrect_command_data');
                    exit;
                }
            } else {
                $cmd_data = $msg_content[1];

                $reg = '/' . $cmd->data_regex . '/';
                if (preg_match($reg, $cmd_data, $matches) > 0) {
                    //形成 命令->参数 键值对
                    $data->$command = $cmd_data;
                } else {
                    //参数不符合命令规则
                    $this->_sendMsg($msg, $this->_getCmd('err_incorrect_command_data'));
                    log_message('error', 'err_incorrect_command_data');
                    exit;
                }
            }
        }

        //保存用户数据
        if (!$this->_saveDatas($msg->msgArr['user_opn_id'], $command, $cmd->id, $data)) {
            $this->_sendMsg($msg, $this->_getCmd('err_save_user_datas_failed'));
            log_message('error', 'err_save_user_datas_failed');
            exit;
        }

        /*
         * 若一个命令 with_plugin = 1 表示一个命令流程结束,将前期获取到的userdatas转入第三方处理
         * 第三方目前定义为library,建议放置在thirdparty目录下加载
         * 
         * $this->load->library($plugin_name);//载入指定的library
         * $this->$plugin_name->$plugin_funtion($userdatas);//载入library后执行指定的函数并传入userdatas,该函数可返回回复信息
         */
        if (1 === intval($cmd->is_with_plugin)) {
            $lib = $cmd->plugin_name;
            $fun = $cmd->plugin_function;
            //获取最终用户数据
            $udatas = $this->_getDatas($msg->msgArr['user_opn_id']);
            //载入指定的library
            $this->load->add_package_path(APPPATH . 'third_party/' . $lib);
            $this->load->library($lib);
            //载入library后执行指定的function并传入userdatas
            $reply = $this->$lib->$fun($udatas);
            /*
             * 函数返回的消息必须是按照正常reply结构组成的对象,否则发送回复会失败
             */
            if (!empty($reply) && is_object($reply)) {
                $this->_sendMsg($msg, $reply);
            }
            exit;
        }

        /*
         * 检查父命令为当前命令的子命令,获取其保存用户数据,提示用户不需重复输入
         * 仅当回复类型设置为text且不为结束命令时有效
         */
        if (!empty($cmd->content) && (0 == $cmd->is_with_plugin)) {
            $ccmd = $this->_getChildCmd($cmd->id);
            //检查子命令是否需要数据
            if ($ccmd && $ccmd->data_regex) {
                $ccommand = $ccmd->command;
                //子命令是否保存有数据
                if (isset($data->$ccommand)) {
                    $tip = "\n" . lang('use_saved_data_note1')
                            . $ccommand . $cmd_sign . $data->$ccommand
                            . lang('use_saved_data_note2') . $ccommand . $cmd_sign;
                    //与原有信息组合
                    $cmd->content .= $tip;
                }
            }
        }
        //回复
        $this->_sendMsg($msg, $cmd);
    }

    /*
     * 获取子命令
     */

    private function _getChildCmd($command_id) {
        $this->load->model('commands_model', 'cmd');

        $c = $this->cmd->getChild($command_id);
        if ($c) {
            return $c;
        }
        return FALSE;
    }

    /*
     * 保存用户数据
     */

    private function _saveDatas($user_opn_id, $command, $command_id, $data, $confirm = 0) {
        $this->load->model('datas_model', 'datas');

        return $this->datas->edit($user_opn_id, $command, $command_id, json_encode($data), $confirm);
    }

    /*
     * 处理文本类信息
     */

    private function _MsgText($msg) {
        if ($this->_isTextCmd($msg)) {
            //转到命令处理
            $this->_MsgCmd($msg);
            exit;
        }
        $key = json_decode($msg->msgArr['content'])->Content;
        $keyword = $this->_getKeyword($msg->msgArr['msgtype_id'], $key);
        if (FALSE === $keyword) {
            //未查询到合适的关键字
            $this->_sendHelpMsg($msg, 'no_match_keyword');
            exit;
        }

        $reply = $this->_getReply(intval($keyword->id));
        if (FALSE === $reply) {
            //指定关键字没有匹配的回复
            $this->_sendHelpMsg($msg, 'no_match_reply_for_keyword');
            exit;
        }

        $this->_sendMsg($msg, $reply);
    }

    /*
     * 获取回复
     */

    private function _getReply($keyword_id) {
        $this->load->model('reply_model', 'reply');

        $replys = $this->reply->getReplys($keyword_id);
        if ($replys) {
            return $replys[array_rand($replys)];
        }
        return FALSE;
    }

    /*
     * 回复消息
     */

    private function _sendMsg($msg, $reply_msg) {
        $this->load->model('msgtype_model', 'msgtype');

        //获取回复类型的文本值并写入对象,以便执行发送信息操作
        if (!empty($reply_msg->reply_msgtype_id)) {
            $reply_msg->reply_msgtype = $this->msgtype->getMsgtype(intval($reply_msg->reply_msgtype_id))->type_name;
        }

        $msg->reply_msg = $reply_msg;

        $this->wechat->sendMsg($msg);
    }

    /*
     * 处理事件类信息
     */

    private function _MsgEvent($msg) {
    	$m = json_decode($msg->msgArr['content']);
        $event = $m->Event;
        if($m->EventKey){
        	$event = $m->EventKey;
        }

        $keyword = $this->_getKeyword($msg->msgArr['msgtype_id'], $event);
        if (FALSE === $keyword) {
            //未查询到合适的关键字
            $this->_sendHelpMsg($msg, 'no_match_keyword');
            exit;
        }

        $this->load->model('reply_model', 'reply');

        $replys = $this->reply->getReplys(intval($keyword->id));
        if (FALSE === $replys) {
            //指定关键字没有匹配的回复
            $this->_sendHelpMsg($msg, 'no_match_reply_for_keyword');
            exit;
        }

        $this->_sendMsg($msg, $replys[array_rand($replys)]);
    }

}